﻿using System;
using System.Threading;
using System.ServiceModel;
using System.Collections;
using System.Collections.Generic;
using System.IO;

using OCXS;
using Conf = OCXS.OCXSServiceLibrary.Configuration;
using OCXS.OCXSServiceLibrary.OCXSEnvironment;
using OCXS.OCXSServiceLibrary.OPCXMLDA10;

namespace OCXS {

	namespace OCXSServiceLibrary {

		/// <summary>
        /// Class implementing the OCXS Service Interface. See <see cref="OCXS.OCXSServiceLibrary.IOCXSService"/> for more detail.
		/// </summary>
        /// <remarks>
        /// <para>
        /// This class provides a concrete implementation for the OCXS Service Contract.
        /// </para>
        /// <para>
        /// The Service has an internal status represented by its members. The Service is meant to be long lasting; current
        /// implementation privileges an IIS-hosted service hosting approach (extensible to other hosting solutions).
        /// </para>
        /// <para>
        /// <b>NOTE: </b> Current implementation provides a default-constructed service. It is possible according
        /// to the Microsoft .NET Service Hosting Architecture) to implement a parametrized-constructed version of
        /// the service. To do so, please follow the Microsoft .NET Service Hosting Architecture specifications.<br />
        /// Given the usage of a parameterless constructed service, many configuration data need to be hardcoded in this file
        /// without providing a good configuration decoupling system. Should you be willing to use this implementation, please remind
        /// to change all machine-specific configuration parameters (marked in this documentation as `MACHINE-SPECIFIC`) to fit your
        /// machine configuration and rebuild the project.
        /// </para>
        /// </remarks>
        [ServiceBehavior(
            InstanceContextMode = InstanceContextMode.Single,
            ConcurrencyMode = ConcurrencyMode.Multiple)]
        public partial class OCXSService : IOCXSService {

            #region Constants

            /// <summary>
            /// Name of the configuration file loaded by the service at startup. [MACHINE-SPECIFIC]
            /// </summary>
            /// <remarks>
            /// The file is fetched through HTTP. Change this if you want to rename configuration file.
            /// </remarks>
            protected const string ConfigurationFileName = @"ocxs.config.xml";

            /// <summary>
            /// HTTP URL to your OCXS Web Application, holding either the configuration file and the Service. [MACHINE-SPECIFIC]
            /// </summary>
            /// <remarks>
            /// Change this parameter according to your IIS configuration (Virtual Directories, etc...).
            /// </remarks>
            protected const string ConfigurationPathName = @"http://localhost:82/OCXS/";

            /// <summary>
            /// Subscription Garbage Collector activation period (milliseconds).
            /// </summary>
            /// <remarks>
            /// You should set this constant accordingly to the service expected subscription throughput.
            /// </remarks>
            protected const int SubscriptionCollectionPeriod = 20000;

            #endregion

            #region Resources

            /// <summary>
            /// Represents the centralized structure to store variables.
            /// </summary>
            /// <remarks>
            /// <para>
            /// This container is filled at service startup, according to the configuration file. <br />
            /// Generators get a reference to each entry of this container, editing the value of each variable.
            /// </para>
            /// <para>
            /// Each variable is accessed through its identifier (path + name). The container returns the variable
            /// and its associated locking object, which provides mutual exclusion access.
            /// </para>
            /// </remarks>
            private Dictionary< String, KeyValuePair< Conf.VariableType, Object > > dataStore;

            /// <summary>
            /// Represents the pool of threads for generating variables values.
            /// </summary>
            /// <remarks>
            /// <para>
            /// The pool is filled with generators at service startup according to the configuration file.
            /// </para>
            /// <para>
            /// Each generator is accessed through the associated generated variable identifier.
            /// </para>
            /// </remarks>
            private Dictionary< String, Thread > threadPool;

            /// <summary>
            /// Lock for the <see cref="OCXS.OCXSServiceLibrary.OCXSService.subscriptionTable"/>.
            /// </summary>
            object subscriptionTablePadlock;

            /// <summary>
            /// Represents the centralized structure to store subscriptions.
            /// </summary>
            /// <remarks>
            /// <para>
            /// Each time a new subscription is requested, an entry in inserted in the table.
            /// </para>
            /// <para>
            /// This table is managed by a dedicated Subscription Garbage Collector periodically activating according to 
            /// the <see cref="OCXS.OCXSServiceLibrary.OCXSService.SubscriptionCollectionPeriod"/>.
            /// </para>
            /// <para>
            /// Each <c>SubscriptionRecord</c> is accessed through its Subscription Identifier, in the form of a Microsoft
            /// Registry Formatted GUID.
            /// </para>
            /// </remarks>
            private Dictionary< String, SubscriptionRecord > subscriptionTable;

            #endregion

            /// <summary>
			/// Default Service Constructor.
			/// </summary>
            /// <remarks>
            /// <para>
            /// The Service is initialized at construction time with default parameters.
            /// </para>
            /// <para>
            /// Configuration is loaded at construction time. 
            /// </para>
            /// <para>
            /// Variables and associated generators are created at construction time according to the configuration file.
            /// Gerenerators are stared too.
            /// </para>
            /// <para>
            /// The subscription table is created and initialized at construction time (empty).
            /// </para>
            /// </remarks>
			public OCXSService() {

                Conf.OCXSConfigurationManager ocxscm =
                    new Conf.OCXSConfigurationManager(
                        ConfigurationPathName + ConfigurationFileName);                   

                // Check
                if (ocxscm.ConfigurationManagerException != null)
                    throw new Exception("Could not start service: bad configuration.");

                // Parse good
                Conf.VariableType[] vars = ocxscm.ParsedVariables;

                if (vars == null || vars.Length == 0)
                    throw new Exception("Could not start service: no variables found in configuration.");

                // Variables good
                this.dataStore = new Dictionary<string, KeyValuePair<Conf.VariableType, Object>>();
                this.threadPool = new Dictionary<string, Thread>();
                this.subscriptionTable = new Dictionary<string, SubscriptionRecord>();
                this.subscriptionTablePadlock = new Object();

                // Starting subscription collector
                (new Thread(this.SubscriptionCollector)).Start();

                // Setting collections and starting threads
                foreach (Conf.VariableType v in vars) {

                    // Adding variable
                    KeyValuePair<Conf.VariableType, Object> data = new KeyValuePair<Conf.VariableType, Object>(v, new Object());
                    this.dataStore.Add(v.VariablePath + v.VariableName, data);

                    // Creating thread to manage this variable
                    Thread t = new Thread(this.VariableGenerator);
                    this.threadPool.Add(v.VariablePath + v.VariableName, t);

                    //Starting thread;
                    t.Start(data);

                }

            }

            /// <summary>
            /// Destructor.
            /// </summary>
            /// <remarks>
            /// Current implementation does not provide any soft shutdown sequence.<br />
            /// In order to stop the service it is necessary to do it manually.
            /// </remarks>
            ~OCXSService() { 
                // Current Service disposal policy: none.
            }

            #region Threads execution

            /// <summary>
            /// Generators' execution flow method.
            /// </summary>
            /// <remarks>
            /// <para>
            /// Each running generator executes this flow.
            /// </para>
            /// <para>
            /// The flow periodically writes the provided variable, according to the specified variable generator activation period.
            /// </para>
            /// </remarks>
            /// <param name="d">Data the thread will use.</param>
            [STAThread]
            private void VariableGenerator(object d) {

                // Casting and getting data
                KeyValuePair<Conf.VariableType, Object> data = (KeyValuePair<Conf.VariableType, Object>)d;
                
                // Getting synthetizer
                Synthetizer s = Synthetizer.GetSynthetizer(data.Key);

                // Getting frequency
                int sleep = data.Key.VariableGenerator.PeriodUpdate; // In ms

                // Looping
                while (true) {

                    // Updating values
                    lock (data.Value) { /* Lock mutex */

                        float val = s.SynthValue();
                        data.Key.VariableValue = val;

                    } /* unlock mutex */

                    // Sleeping
                    Thread.Sleep(sleep);

                }

            } /* VariableGenerator */

            #endregion

            #region Resource management

            /// <summary>
            /// Subscription Garbage Collector's (SGC) execution flow method.
            /// </summary>
            /// <remarks>
            /// <para>
            /// The SGC is activated at Service construction time. Once run the SCG periodically scans the
            /// <see cref="OCXS.OCXSServiceLibrary.OCXSService.subscriptionTable"/> looking for disposed 
            /// entries, and frees them.
            /// </para>
            /// </remarks>
            [STAThread]
            private void SubscriptionCollector() {

                string buffer = "";

                while (true) {

                    buffer = "";

                    lock (this.subscriptionTablePadlock) {
                        
                        foreach (KeyValuePair<string, SubscriptionRecord> se in this.subscriptionTable) {
                            
                            if (se.Value.Disposed) {
                                buffer = se.Key;
                                break;
                            }
                        }

                        if (buffer != "")
                            this.subscriptionTable.Remove(buffer);
                    } /* lock */

                    if (buffer != "")
                        Thread.Sleep(SubscriptionCollectionPeriod);

                }

            } /* SubscriptionCollector */

            /// <summary>
            /// Gets the <c>SubscriptionRecord</c> associated with the specified key.
            /// </summary>
            /// <param name="sid">The Subscription identifier.</param>
            /// <param name="sr">The Subscription if found.</param>
            /// <returns><c>true</c> if the subscription exists, otherwise <c>false</c>.</returns>
            private bool TryGetSubscriptionRecordBySID(string sid, out SubscriptionRecord sr) {

                lock (this.subscriptionTablePadlock) {

                    return this.subscriptionTable.TryGetValue(sid, out sr);
                
                }

            }

            #endregion

            #region Miscellaneous

            /// <summary>
            /// Returns a factorized <c>ReplyBase</c>.
            /// </summary>
            /// <remarks>
            /// Called at the beginning of every service request, this helper 
            /// method fills a <c>ReplyBase</c> without correctly specifying a ReplyTime, 
            /// which needs to be modified by the caller right before returning 
            /// from the service call.
            /// </remarks>
            /// <returns></returns>
            private ReplyBase GetReplyBase(string clientRequestHandle = "", string localeID = "en-us") {

                ReplyBase RB = new ReplyBase();
                RB.RcvTime = DateTime.Now;
                // ReplyTime to be set before returning in the calling method.
                RB.ClientRequestHandle = clientRequestHandle;
                RB.RevisedLocaleID = "en-us";
                RB.ServerState = serverState.running; // TODO: Revise in future implementations.

                return RB;

            } /* GetReplyBase */

            /// <summary>
            /// Gets the <c>VariableType</c> associated with the specified key.
            /// </summary>
            /// <param name="varpath">Variable path.</param>
            /// <param name="varname">Variable name.</param>
            /// <returns>A <c>VariableType</c> representing the requested variable, <c>null</c> otherwise.</returns>
            private Conf.VariableType GetVariableById(string varpath, string varname) {

                KeyValuePair<Conf.VariableType, Object> v;

                if (this.dataStore.TryGetValue((varpath + varname), out v)) {

                    Conf.VariableType retVal = null;

                    // Critical section
                    lock (v.Value) {
                        retVal = (Conf.VariableType)v.Key.Clone();
                    }

                    return retVal;
                }

                // Key not found
                return null;
            }

            /// <summary>
            /// Gets all properties for the specified Item.
            /// </summary>
            /// <remarks>
            /// Property values are returned on demand.
            /// </remarks>
            /// <param name="itempath">Item path.</param>
            /// <param name="itemname">Item name.</param>
            /// <param name="returnvalues">Specifies whether or not property values should be returned.</param>
            /// <returns>An <c>ItemProperty</c> array if found, <c>null</c> otherwise.</returns>
            private ItemProperty[] GetItemPropertiesByItemId(string itempath, string itemname, bool returnvalues) {

                string id = itempath + itemname;
                Conf.VariableType variable = this.GetVariableById(itempath, itemname);

                if (variable != null) {
                    
                    // Creating array
                    ItemProperty[] props = new ItemProperty[3];

                    // Description property
                    props[0] = new ItemProperty();
                    props[0].Description = "Description of the variable.";
                    props[0].ItemName = variable.VariableName;
                    props[0].ItemPath = variable.VariablePath;
                    props[0].Name = new System.Xml.XmlQualifiedName("VariableDescription");
                    props[0].ResultID = new System.Xml.XmlQualifiedName("", "");
                    props[0].Value = (returnvalues) ? variable.VariableDescription : null;

                    // VariableType property
                    props[1] = new ItemProperty();
                    props[1].Description = "OCXS type of the variable.";
                    props[1].ItemName = variable.VariableName;
                    props[1].ItemPath = variable.VariablePath;
                    props[1].Name = new System.Xml.XmlQualifiedName("VariableType");
                    props[1].ResultID = new System.Xml.XmlQualifiedName("", "");
                    props[1].Value = (returnvalues) ? variable.VariableT.ToString() : null;

                    // VariableMU property
                    props[2] = new ItemProperty();
                    props[2].Description = "OCXS measurement unit of the variable.";
                    props[2].ItemName = variable.VariableName;
                    props[2].ItemPath = variable.VariablePath;
                    props[2].Name = new System.Xml.XmlQualifiedName("VariableMU");
                    props[2].ResultID = new System.Xml.XmlQualifiedName("", "");
                    props[2].Value = (returnvalues) ? variable.VariableMU : null;

                    return props;

                } else
                    return null;

            } /* GetItemPropertiesByItemId */

            /// <summary>
            /// Builds and returns an OPCError according to provided parameters.
            /// </summary>
            /// <param name="name">Name for the XMLQualifiedName corresponding to the current error.</param>
            /// <param name="text">Verbose description for the error.</param>
            /// <returns>An OPCError according to provided parameters.</returns>
            private OPCError BuildOPCError(string name, string text) {
                OPCError oe = new OPCError();
                oe.ID = new System.Xml.XmlQualifiedName(name, "http://opcfoundation.org/webservices/XMLDA/1.0/");
                oe.Text = text;

                return oe;
            }

            #endregion

        }

    } /* OCXSServiceLibrary */

} /* OCXS */
